/******************************************************************************* * Signavio Core Components * Copyright (C) 2012 Signavio GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package de.hpi.bpmn2_0.factory; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.oryxeditor.server.diagram.generic.GenericShape; import org.oryxeditor.server.diagram.label.LabelSettings; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; import com.sun.xml.bind.StringInputStream; import de.hpi.bpmn2_0.annotations.Property; import de.hpi.bpmn2_0.annotations.StencilId; import de.hpi.bpmn2_0.exceptions.BpmnConverterException; import de.hpi.bpmn2_0.factory.configuration.Configuration; import de.hpi.bpmn2_0.factory.edge.AssociationFactory; import de.hpi.bpmn2_0.factory.edge.ConversationLinkFactory; import de.hpi.bpmn2_0.factory.edge.MessageFlowFactory; import de.hpi.bpmn2_0.factory.edge.SequenceFlowFactory; import de.hpi.bpmn2_0.factory.node.ChoreographyActivityFactory; import de.hpi.bpmn2_0.factory.node.ChoreographyParticipantFactory; import de.hpi.bpmn2_0.factory.node.ConversationFactory; import de.hpi.bpmn2_0.factory.node.ConversationParticipantFactory; import de.hpi.bpmn2_0.factory.node.DataObjectFactory; import de.hpi.bpmn2_0.factory.node.DataStoreFactory; import de.hpi.bpmn2_0.factory.node.EndEventFactory; import de.hpi.bpmn2_0.factory.node.GatewayFactory; import de.hpi.bpmn2_0.factory.node.GroupFactory; import de.hpi.bpmn2_0.factory.node.ITSystemFactory; import de.hpi.bpmn2_0.factory.node.IntermediateCatchEventFactory; import de.hpi.bpmn2_0.factory.node.IntermediateThrowEventFactory; import de.hpi.bpmn2_0.factory.node.LaneFactory; import de.hpi.bpmn2_0.factory.node.MessageFactory; import de.hpi.bpmn2_0.factory.node.ParticipantFactory; import de.hpi.bpmn2_0.factory.node.ProcessParticipantFactory; import de.hpi.bpmn2_0.factory.node.StartEventFactory; import de.hpi.bpmn2_0.factory.node.SubprocessFactory; import de.hpi.bpmn2_0.factory.node.TaskFactory; import de.hpi.bpmn2_0.factory.node.TextannotationFactory; import de.hpi.bpmn2_0.model.BaseElement; import de.hpi.bpmn2_0.model.Documentation; import de.hpi.bpmn2_0.model.FlowElement; import de.hpi.bpmn2_0.model.bpmndi.di.DiagramElement; import de.hpi.bpmn2_0.model.extension.ExtensionElements; import de.hpi.bpmn2_0.model.extension.signavio.SignavioLabel; import de.hpi.bpmn2_0.model.extension.signavio.SignavioMetaData; import de.hpi.bpmn2_0.model.misc.Auditing; import de.hpi.bpmn2_0.model.misc.Monitoring; import de.hpi.bpmn2_0.transformation.Constants; import de.hpi.bpmn2_0.transformation.Diagram2BpmnConverter; /** * This is the abstract factory that offers methods to create a process element * and a related diagram element from a {@link GenericShape}. */ public abstract class AbstractBpmnFactory { private static List<Class<? extends AbstractBpmnFactory>> factoryClasses = new ArrayList<Class<? extends AbstractBpmnFactory>>(); /** * Manual initialization of factory classes list. Is there a pattern for automatic initialization * except reading the jar file? */ static { /* Standard BPMN 2.0 */ factoryClasses.add(AbstractActivityFactory.class); factoryClasses.add(SubprocessFactory.class); factoryClasses.add(TaskFactory.class); factoryClasses.add(AbstractEdgesFactory.class); factoryClasses.add(ConversationLinkFactory.class); factoryClasses.add(MessageFlowFactory.class); factoryClasses.add(SequenceFlowFactory.class); factoryClasses.add(AssociationFactory.class); factoryClasses.add(ChoreographyActivityFactory.class); factoryClasses.add(ChoreographyParticipantFactory.class); factoryClasses.add(ConversationFactory.class); factoryClasses.add(ConversationParticipantFactory.class); factoryClasses.add(DataObjectFactory.class); factoryClasses.add(DataStoreFactory.class); factoryClasses.add(EndEventFactory.class); factoryClasses.add(GatewayFactory.class); factoryClasses.add(GroupFactory.class); factoryClasses.add(IntermediateCatchEventFactory.class); factoryClasses.add(IntermediateThrowEventFactory.class); factoryClasses.add(ITSystemFactory.class); factoryClasses.add(LaneFactory.class); factoryClasses.add(MessageFactory.class); factoryClasses.add(ParticipantFactory.class); factoryClasses.add(ProcessParticipantFactory.class); factoryClasses.add(StartEventFactory.class); factoryClasses.add(TextannotationFactory.class); } public static List<Class<? extends AbstractBpmnFactory>> getFactoryClasses() { List<Class<? extends AbstractBpmnFactory>> factories = new ArrayList<Class<? extends AbstractBpmnFactory>>(factoryClasses); Constants c = Diagram2BpmnConverter.getConstants(); if(c == null) { return factories; } factories.addAll(c.getAdditionalFactoryClasses()); return factories; } /** * Creates a process element based on a {@link GenericShape}. * * @param shape * The resource shape * @return The constructed process element. */ protected abstract BaseElement createProcessElement(GenericShape shape) throws BpmnConverterException; /** * Creates a diagram element based on a {@link GenericShape}. * * @param shape * The resource shape * @return The constructed diagram element. */ protected abstract DiagramElement createDiagramElement(GenericShape shape); /** * Creates BPMNElement that contains DiagramElement and ProcessElement * * @param shape * The resource shape. * @return The constructed BPMN element. */ public abstract BPMNElement createBpmnElement(GenericShape shape, BPMNElement parent) throws BpmnConverterException; /** * Sets attributes of a {@link BaseElement} that are common for all * elements. * * @param element * The BPMN 2.0 element * @param shape * The resource shape */ protected void setCommonAttributes(BaseElement element, GenericShape shape) { element.setId(shape.getResourceId()); /* Documentation */ String documentation = shape.getProperty("documentation"); if (documentation != null && !(documentation.length() == 0) && element.getDocumentation().size() == 0) element.getDocumentation().add(new Documentation(documentation)); /* Common FlowElement attributes */ if(element instanceof FlowElement) { /* Auditing */ String auditing = shape.getProperty("auditing"); if (auditing != null && !(auditing.length() == 0)) ((FlowElement) element).setAuditing(new Auditing(auditing)); /* Monitoring */ String monitoring = shape.getProperty("monitoring"); if (monitoring != null && !(monitoring.length() == 0)) ((FlowElement) element).setMonitoring(new Monitoring(monitoring)); /* Name */ String name = shape.getProperty("name"); if(name != null && !(name.length() == 0)) { ((FlowElement) element).setName(name); } } } /** * Sets common fields for the visual representation. * * @param diaElement * The BPMN 2.0 diagram element * @param shape * The resource shape */ protected void setVisualAttributes(DiagramElement diaElement, GenericShape shape) { diaElement.setId(shape.getResourceId() + "_gui"); } protected BaseElement invokeCreatorMethod(GenericShape shape) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, BpmnConverterException { /* Retrieve the method to create the process element */ for (Method method : Arrays .asList(this.getClass().getMethods())) { StencilId stencilIdA = method.getAnnotation(StencilId.class); if (stencilIdA != null && Arrays.asList(stencilIdA.value()).contains( shape.getStencilId())) { /* Create element with appropriate method */ BaseElement createdElement = (BaseElement) method.invoke(this, shape); /* Invoke generalized method to set common element attributes */ this.setCommonAttributes(createdElement, shape); return createdElement; } } throw new BpmnConverterException("Creator method for shape with id " + shape.getStencilId() + " not found"); } protected BaseElement invokeCreatorMethodAfterProperty(GenericShape shape) throws BpmnConverterException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { for (Method method : Arrays .asList(this.getClass().getMethods())) { Property property = method.getAnnotation(Property.class); if (property != null && Arrays.asList(property.value()).contains( shape.getProperty(property.name()))) { /* Create element */ BaseElement createdElement = (BaseElement) method.invoke(this, shape); /* Invoke generalized method to set common element attributes */ this.setCommonAttributes(createdElement, shape); return createdElement; } } throw new BpmnConverterException("Creator method for shape with id " + shape.getStencilId() + " not found"); } public BPMNElement createBpmnElement(GenericShape shape, Configuration configuration) throws BpmnConverterException { BPMNElement bpmnElement = createBpmnElement(shape, new BPMNElement(null, null, null)); if(bpmnElement != null && bpmnElement.getNode() != null) { bpmnElement.getNode()._diagramElement = bpmnElement.getShape(); setCustomAttributes(shape, bpmnElement.getNode(), configuration.getMetaData()); // handle external extension elements like from Activiti // try { // reinsertExternalExtensionElements(shape, bpmnElement); // } catch (Exception e) { // // } // Apply processid from shape used for round tripping if existing if(bpmnElement.getNode() instanceof FlowElement) { ((FlowElement) bpmnElement.getNode()).setProcessid(shape.getProperty("processid")); } } return bpmnElement; } private void setCustomAttributes(GenericShape shape, BaseElement node, Map<String, Set<String>> metaData) { if(shape == null || node == null || metaData == null) return; Set<String> attributeNames = metaData.get(shape.getStencilId()); if(attributeNames == null) { return; } ExtensionElements extElements = node.getOrCreateExtensionElements(); Iterator<String> iterator = attributeNames.iterator(); while(iterator.hasNext()) { String attributeKey = iterator.next(); String attributeValue = shape.getProperty(attributeKey); /* Avoid undefined Signavio meta attributes */ if(attributeValue == null) { continue; } SignavioMetaData sigMetaData = new SignavioMetaData(attributeKey, attributeValue); extElements.getAny().add(sigMetaData); } } /** * Checks if the shapes has content in the externalextensionelements * property and writes those XML Elements back to the extension elements * part of each element. * * @param shape * @param el * @throws ParserConfigurationException * @throws IOException * @throws SAXException */ protected void reinsertExternalExtensionElements(GenericShape shape, BPMNElement el) throws ParserConfigurationException, SAXException, IOException { reinsertOtherAttributes(shape, el); String exElXml = shape.getProperty("externalextensionelements"); if(exElXml == null || exElXml.length() == 0) return; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); StringInputStream sis = new StringInputStream(exElXml); Document exDoc = builder.parse(sis); if(!exDoc.getFirstChild().getNodeName().equals("external")) { return; } Node n = exDoc.getFirstChild().getFirstChild(); while(n != null) { if(n instanceof Element) { el.getNode().getOrCreateExtensionElements().getAnyExternal().add((Element) n); findNamespaceURIs((Element) n, el); } n = n.getNextSibling(); } } /** * Parse attributes being intended for export under other namespace. * * @param shape * @param el */ private void reinsertOtherAttributes(GenericShape shape, BPMNElement el) { String otherAttrStr = shape.getProperty("otherattributes"); if(otherAttrStr == null || otherAttrStr.length() == 0) { return; } // process as json array containing json objects try { JSONArray a = new JSONArray(otherAttrStr); for(int i = 0; i < a.length(); i++) { JSONObject o = a.getJSONObject(i); String localpart = o.optString("localpart"); String ns = o.optString("ns"); String prefix = o.optString("prefix"); String value = o.optString("value"); if((localpart != null || ns != null || prefix != null) && value != null) { el.getNode().getOtherAttributes().put(new QName((ns != null ? ns : ""), (localpart != null ? localpart : ""), (prefix != null ? prefix : "")), value); } } } catch (JSONException e) { } } private void findNamespaceURIs(Element element, BPMNElement el) { // Map<String,String> nsMapping = new HashMap<String, String>(); if(element.getPrefix() != null && element.getPrefix().length() > 0 && element.getNamespaceURI() != null && element.getNamespaceURI().length() > 0) { el.getExternalNamespaceDefinitions().put(element.getNamespaceURI(), element.getPrefix()); // remove local ns definition element.removeAttribute("xmlns:" + element.getPrefix()); element.getAttribute("xmlns:" + element.getPrefix()); } // return nsMapping; } protected void setLabelPositionInfo(GenericShape<?,?> shape, BaseElement node) { if(shape == null || node == null || shape.getLabelSettings().isEmpty()) { return; } ExtensionElements extElements = node.getOrCreateExtensionElements(); for(LabelSettings settings : shape.getLabelSettings()) { SignavioLabel label = new SignavioLabel(settings.getSettingsMap()); extElements.getAny().add(label); } } }